/******************************************************************************
 * $Id: utils.c,v 1.101 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Various utility functions.
 * @remarks Some inline functions reside at utils.h
 ******************************************************************************/

#include "common.h"
#include "utils.h"

/*
 * Tables for lookup.
 */
char tableWhitespace[256];
char tableCIdentifier[256];     /* pure C */
char tableCishIdentifier[256];  /* C/C++/Java */
char tableSymbol[256];
/*****************************************************************************
 * Must be called to initialize utility functions.
 *****************************************************************************/
void
InitUtils( void )
{
   int i;
  /*
   * Whitespace table.
   */
   CLEAR( tableWhitespace, sizeof(tableWhitespace) );
   tableWhitespace[ (int)' '  ] = 1;
   tableWhitespace[ (int)'\t' ] = 1;
   tableWhitespace[ (int)'\n' ] = 1;
   tableWhitespace[ (int)'\r' ] = 1;

  /*
   * C (pure C) identifier table.
   */
   for ( i = 0; i < 256; ++i )
      tableCIdentifier[i] = isalnum( (char)i ) || i == '_';

  /*
   * C-ish (C/C++/Java) identifier table.
   */
   for ( i = 0; i < 256; ++i )
      tableCishIdentifier[i] = isalnum( (char)i ) || i == '_' || i == '~';

  /*
   * Symbol char table.
   */
   for ( i = 0; i < 256; ++i )
   {
       tableSymbol[i] =  (    (i >= '!' && i <= '/' )
                          || (i >= ':' && i <= '@' )
                          || (i >= '[' && i <= 0x60)
                          || (i >= '{' && i <= '~' )
                        )
                        && i != '_';  /* '_' was included above but exclude it */
   }
   return;
}

/******************************************************************************
 * Parse the next word in a line.
 * @param   pc
 *          Pointer within the current line.
 *          This can point to a memory buffer that contains multiple lines.
 * @param   ppWord (out)
 *          This pointer is assigned to point to the first char of parsed word.
 *          (Will be assigned NULL if no word was parsed.)
 * @param   max
 *          Aborts if this amount of chars were scanned.
 * @return  Positive amount of chars in the next word
 *          -or- FUNC_FAIL if there is no word in the line to parse
 *          -or- FUNC_FAIL_EXCESSIVE if more than "max" chars were scanned.
 ******************************************************************************/
FUNC_X
ParseWord( const char*  pc,
           const char** ppWord,
           int          max )
{
   int size = 0;

#ifndef HYPERSRC_SPEED
#if 0
   /* MAX_WORD_LEN check will cause unnecessary asserts at hilite funcs. */
   g_assert( pc && ppWord && max <= MAX_WORD_LEN );
#else
   g_assert( pc && ppWord );
#endif
#endif

  /*
   * Move past any whitespace.
   */
   *ppWord = PastWhitespace( pc, max );
   if ( *ppWord == NULL ) return FUNC_FAIL;

  /*
   * Is EOL the only char after whitespace?
   */
   if ( IsEol( *ppWord ) ) { *ppWord = NULL; return FUNC_FAIL; }

  /*
   * "ppWord" is now pointing to the first char of a word.
   * Return how many chars are in the word.
   */
#ifndef HYPERSRC_SPEED
g_assert( (*ppWord >= pc)  &&  (*ppWord < (pc + max)) );
#endif
   size = 1;
   while ( ! IsWhitespace( (*ppWord)[size] )  &&  size < max )
   { ++size; }

   if ( size <= max )	/* if size == max then on last iteration size < max */
      return size;
   else
      { *ppWord = NULL; return FUNC_FAIL_EXCESSIVE; }
}

/******************************************************************************
 * Parse the next word that only contains alphanum (and '_') chars.
 * Otherwise, similar to ParseWord().
 ******************************************************************************/
FUNC_X
ParseWordAlphanum( const char*  pc,
                   const char** ppWord,
                   int          max )
{
   int size = 0;

#ifndef HYPERSRC_SPEED
#if 0
   /* MAX_WORD_LEN check will cause unnecessary asserts at hilite funcs. */
   g_assert( pc && ppWord && max <= MAX_WORD_LEN );
#else
   g_assert( pc && ppWord );
#endif
#endif

  /*
   * Move past any whitespace.
   */
   *ppWord = PastWhitespace( pc, max );
   if ( *ppWord == NULL ) return FUNC_FAIL;

  /*
   * Is EOL the only char after whitespace?
   */
   if ( IsEol( *ppWord ) ) { *ppWord = NULL; return FUNC_FAIL; }

  /*
   * "ppWord" is now pointing to the first alphanum char of a word.
   * Return how many alphanum chars are in the word.
   */
#ifndef HYPERSRC_SPEED
g_assert( (*ppWord >= pc)  &&  (*ppWord < (pc + max)) );
#endif
   size = 1;
   while ( (isalnum( (*ppWord)[size] )  ||  (*ppWord)[size] == '_')
           &&  size < max )
   { ++size; }

   if ( size <= max )	/* if size == max then on last iteration size < max */
      return size;
   else
      { *ppWord = NULL; return FUNC_FAIL_EXCESSIVE; }
}

/******************************************************************************
 * Point to the first char of the next line.
 * Supports both UNIX (LF) and MSDOS (CR/LF) line formats.
 * @param   pc
 *          Pointer within the current line.
 * @param   max
 *          Aborts if this amount of chars has been scanned.
 * @return  Returns a pointer to the first char of the next non-empty line.
 *          Returns NULL if this was the last line
 *          -or- "max" was exceeded.
 ******************************************************************************/
const char*
NextLine( const char* pc,
          int         max )
{
   const char* end = pc + max;
#ifndef HYPERSRC_SPEED
   g_assert( pc );
#endif
   for ( ; *pc && pc < end; ++pc )
   {
      if ( UX(*pc) == '\n' ) return pc+1;
   }
   return NULL;
}

/******************************************************************************
 * Return a pointer to the specified line.
 * @param   pc
 *          Pointer to a memory buffer that contains line(s).
 * @param   line
 *          Zero-based number of line.
 * @param   maxLineSize
 *          Fails if a line has more than this many chars.
 * @return  Pointer to line specified by "line" in the buffer "pc"
 *          -or- NULL upon failure.
 ******************************************************************************/
const char*
ToLine( const char*	pc,
        int   line,
        int   maxLineSize )
{
   int i;

#ifndef HYPERSRC_SPEED
g_assert( pc  &&  (maxLineSize <= MAX_LINE_LEN) );
g_return_val_if_fail( line < MAX_LINES, NULL );
#endif

   for ( i = 0; i < line; ++i )
   {
     /*
      * Try to move to the first char of the next line.
      */
      pc = NextLine( pc, maxLineSize );

     /*
      * Failed?
      */
      if ( pc == NULL ) return NULL;
   }

   return pc;
}

/*****************************************************************************
 * Return a pointer to the first char of a EOL char(s).
 * @verbatim
 * Eg, for UNIX, returns a pointer to '\n'.
 *     for MSDOS, returns a pointer to CR of a CR/LF pair.
 * @endverbatim
 * @param   pc
 *          Pointer to a line.
 * @param   max
 *          Fails if a line has more than this many chars.
 *****************************************************************************/
char*
ToEol( const char* pLine,
       int         max )
{
   int	i;

#ifndef HYPERSRC_SPEED
g_assert( pLine );
#endif

   for ( i = 0; i < max; ++i )
   {
     /*
      * Fail if null byte found.
      */
      if ( ! pLine[i] ) return NULL;

     /*
      * EOL?
      */
      if ( IsEol( &pLine[i] ) )
         return (char*) &pLine[i];
   }

  /*
   * Overrun.
   */
   return NULL;
}

/******************************************************************************
 * Derive the (zero-based) number of a line from its index.
 * @param   pText
 *          Pointer to the start of the text.
 * @param   idxLine
 *          Index (based from pText) to the line.
 ******************************************************************************/
gint
LineIdx2LineNum( gchar* pText,
                 gint   idxLine )
{
   gint	line   = 0;
   gint	idx    = 0;

g_assert( pText );

  /*
   * Start from index 0 to the index of the line,
   * counting each EOL.
   */
   ++line; //lv
   while ( idx < idxLine )
   {
      if ( IsEol( &pText[idx] ) )
         ++line;

      ++idx;
   }

   return line;
}

/******************************************************************************
 * If a pointer is pointing to any recognizable EOL, then return a pointer past the EOL.
 * @param   pc
 *          Pointer to a possible EOL (can be pointing to anything).
 * @return  A pointer after any recognizable EOL pointed to by "pc"
 *          -or- returns "pc".
 ******************************************************************************/
const char*
PastEol( const char* pc )
{
#ifndef HYPERSRC_SPEED
g_assert( pc );
#endif

  /*
   * Check for MSDOS EOL first (because its first char looks like a UNIX EOL!).
   */
   if ( IsEolMsdos( pc ) ) return pc+2;

  /*
   * Check for UNIX EOL.
   */
   if ( IsEolUnix( pc ) ) return pc+1;

  /*
   * Not pointing to any recognizable EOL.
   * Return what was passed.
   */
   return pc;
}

/*****************************************************************************
 * Extract a line from a buffer of multiple lines.
 * @return The extracted line as a separate string.
 * -- The string should be freed only by DeleteString(). --
 *****************************************************************************/
char*
ExtractLine( const char* pLine,
             int         maxLineLen )
{
   const char*	pEol = NULL;
         char*	str;

  /*
   * Is the line terminated by \n ?
   */
   pEol = ToEol( pLine, maxLineLen );
   if ( pEol == NULL )
   {
     /*
      * Assume the line is terminated by a null byte.
      */
      pEol = pLine + StrLen( pLine );
   }

   str = CreateString( pEol - pLine );
   CopyChars( str, pLine, pEol - pLine );

   return str;
}

/*
 * return the Ascii Char string length from the UTF-8 string's length.
 */
gsize
GetAsciiStringLen(const gchar *utf8_str, const glong utf8_len)
{
	// check utf8_str available-- ignored
	gchar * utf8_sub_str = g_utf8_offset_to_pointer(utf8_str, utf8_len);
	if ( utf8_len >= 0 )
		return (gsize)(utf8_sub_str - utf8_str);
	else
		return (gsize)(utf8_str - utf8_sub_str);
}

/*****************************************************************************
 * Helper to create a GString assigned with a string with a specified length.
 * Later when GString is freed, its char data should be freed also,
 * eg g_string_free( GString*, TRUE ).
 *****************************************************************************/
GString*
Segment2GString( char* pInit,
                 int   len )
{
   GString* pGString;
   int      i;
   gsize asciiStrLen = 0;
g_assert( pInit  &&  len >= 0  &&  len < MAX_STRING_LEN );

   if ( !g_utf8_validate(pInit, -1, NULL) )
   {
	   DebugMsg("unavailable utf8 string:%s\n", pInit);
	   return NULL;
   }
  /*
   * Create empty GString.
   */
   asciiStrLen = GetAsciiStringLen(pInit, len);
   pGString = g_string_sized_new( asciiStrLen + 5 );//5 for safe

#if 0
  /*
   * Append each char of specified regular string into GString.
   */
   for ( i = 0; i < len; i++ )
   {
     /*
      * Break if this char is null.
      */
      if ( ! pInit[i] ) break;

     /*
      * Append this char.
      */
      pGString = g_string_append_c( pGString, pInit[i] );
   }
#endif
   //insert string
//   g_utf8_strncpy(pGString->str, pInit, len); // cause an error
//   pGString->len = asciiStrLen;
   pGString = g_string_append_len(pGString,pInit,asciiStrLen);


  /*
   * Append null char.
   */
   pGString = g_string_append_c( pGString, '\0' );
   pGString->len --;

   DebugMsg("***** func=%s\norit_len=%d\tnew_len=%d\tstr=%s\n",__func__,len,pGString->len,pGString->str);

   return pGString;
}

/*****************************************************************************
 * Compute the length of a string without overshooting MAX_STRING_LEN.
 * Does NOT count null byte.
 * @return  Size of string.
 *          If string size is > MAX_STRING_LEN then MAX_STRING_LEN
 *          will be returned.
 *****************************************************************************/
#ifndef HYPERSRC_SPEED
size_t
StrLen( const char* pStr )
{
   char* pNullChar;

g_assert( pStr );

#ifdef USE_STD_STRING_FUNCS
   return strlen( pStr );
#endif

  /*
   * Is passed ptr NULL?
   */
   if ( pStr == NULL ) return 0;

  /*
   * Scan for a null-char without overshooting string.
   */
   pNullChar = memchr( pStr, 0, MAX_STRING_LEN );
   if ( pNullChar != NULL )
      return pNullChar - pStr;

  /*
   * No null-char was found.
   * String is too long, return MAX_STRING_LEN.
   */
   return MAX_STRING_LEN;
}
#endif /* !HYPERSRC_SPEED */

/*****************************************************************************
 * Find a substring (case-sensitive).
 * Does so reasonably safe.
 * This is similar to strstr() except it is safer.
 * @return  Returns NULL if either string is longer than MAX_STRING_LEN.
 *          Returns NULL if either string is empty.
 *****************************************************************************/
#ifndef HYPERSRC_SPEED
char*
FindSubstr( const char* pHaystack,
            const char* pNeedle )
{
   char*	pLimit;
   int      needleSize;

  /*
   * ####################################################
   * FindSubstr(), FindSubstrI() are identical          #
   * except for calling strncmp(), strncasecmp(), resp. #
   * Also, insensitive form uses tolower().             #
   * ####################################################
   */

g_assert( pHaystack && pNeedle );

#ifdef USE_STD_STRING_FUNCS
   return strstr( pHaystack, pNeedle );
#endif

  /*
   * Return NULL if any pointer is NULL -or- any string is empty.
   */
   if (    pHaystack  == NULL
        || pNeedle    == NULL
        || *pHaystack == 0 
        || *pNeedle   == 0 )
      return NULL;

  /*
   * Compute needle size, return NULL if too long.
   */
   needleSize = StrLen( pNeedle );
   if ( needleSize == MAX_STRING_LEN )
      return NULL;

  /*
   * Don't overshoot MAX_STRING_LEN.
   */
   pLimit = (char*)pHaystack + MAX_STRING_LEN;
   while ( pHaystack < pLimit  &&  *pHaystack )
   {
     /*
      * Does this haystack char match first char in needle?
      */
      if ( *pHaystack == *pNeedle )
      {
        /*
         * Without overshooting, compare point in haystack against needle.
         */
         if ( strncmp( pHaystack, pNeedle, needleSize ) == 0 )
            return (char*)pHaystack;
      }

      ++pHaystack;
   }

   return NULL;
}
#endif /* !HYPERSRC_SPEED */

/******************************************************************************
 * Find a substring, ignoring case.
 * Does so reasonably safe.
 * @return  Returns NULL if either string is longer than MAX_STRING_LEN.
 *          Returns NULL if either string is empty.
 ******************************************************************************/
char*
FindSubstrI( const char* pHaystack,
             const char* pNeedle )
{
   char*	pLimit;
   int      needleSize;

  /*
   * ####################################################
   * FindSubstr(), FindSubstrI() are identical          #
   * except for calling strncmp(), strncasecmp(), resp. #
   * Also, insensitive form uses tolower().             #
   * ####################################################
   */

#ifndef HYPERSRC_SPEED
g_assert( pHaystack && pNeedle );
#endif

#ifdef USE_STD_STRING_FUNCS
   return strstr( pHaystack, pNeedle );
#endif

  /*
   * Return NULL if any pointer is NULL -or- any string is empty.
   */
   if (    pHaystack  == NULL
        || pNeedle    == NULL
        || *pHaystack == 0 
        || *pNeedle   == 0 )
      return NULL;

  /*
   * Compute needle size, return NULL if too long.
   */
   needleSize = StrLen( pNeedle );
   if ( needleSize == MAX_STRING_LEN )
      return NULL;

  /*
   * Don't overshoot MAX_STRING_LEN.
   */
   pLimit = (char*)pHaystack + MAX_STRING_LEN;
   while ( pHaystack < pLimit  &&  *pHaystack )
   {
     /*
      * Does this haystack char match first char in needle?
      */
      if ( tolower(*pHaystack) == tolower(*pNeedle) )
      {
        /*
         * Without overshooting, compare point in haystack against needle.
         */
         if ( strncasecmp( pHaystack, pNeedle, needleSize ) == 0 )
            return (char*)pHaystack;
      }

      ++pHaystack;
   }

   return NULL;
}

/*****************************************************************************
 * Find a substring in a segment (not necessarily null-terminated).
 * @param   pHaystack, haystackLen
 *          Pointer/length of segment to search.
 * @param   pNeedle
 *          String that is sought in haystack.
 * @param   caseSens
 *          Whether to be case-sensitive.
 * @return  Returns pointer to string if found, else NULL.
 *          NULL can be returned for a variety of failures, such as
 *          bad pointers or empty strings.
 *****************************************************************************/
char*
FindSubstrSegment( const char* pHaystack,
                   int         haystackLen,
                   const char* pNeedle,
                   gboolean    caseSens )
{
   int	idx;
   int	needleLen;

   if (    pHaystack == NULL
        || haystackLen <= 0
        || haystackLen > MAX_HAYSTACK_LEN
        || pNeedle == NULL )
      return NULL;

   needleLen = strlen( pNeedle );
   if ( needleLen == 0 ) return NULL;

   if ( caseSens )
   {
      for ( idx = 0; idx < haystackLen; ++idx )
      {
         if ( strncmp( &pHaystack[idx], pNeedle, needleLen ) == 0 )
            return (char*) &pHaystack[idx];
      }
   }
   else
   {
      for ( idx = 0; idx < haystackLen; ++idx )
      {
         if ( strncasecmp( &pHaystack[idx], pNeedle, needleLen ) == 0 )
            return (char*) &pHaystack[idx];
      }
   }

   return NULL;
}

/*****************************************************************************
 * Find a substring, reverse search.
 * Does so reasonably safe.
 * @param   pHaystackTop, pHaystackBottom
 *          Highest and lower address of segment to search, inclusive.
 * @param   pNeedle
 *          String that is sought in haystack.
 * @param   pFuncCmp
 *          Either strncmp() or strncasecmp().
 * @return  Returns pointer to string if found, else NULL.
 *          NULL can be returned for a variety of failures, such as
 *          bad pointers or empty strings.
 *****************************************************************************/
char*
FindSubstrReverse( const char* pHaystackTop,
                   const char* pHaystackBottom,
                   const char* pNeedle,
                   P_FUNC_STRCMPX pFuncCmp )
{
   int  needleSize;
   char	needleFirstChar;
   char	haystackTopChar;

  /*
   * These fail cases can occur but aren't considered 
   */
   if ( pHaystackTop == pHaystackBottom ) return NULL;

g_assert(    pHaystackTop
          && pHaystackBottom
          && pNeedle
          && (pHaystackTop > pHaystackBottom)
          && ( (pFuncCmp == strncmp)  ||  (pFuncCmp == strncasecmp)) );

  /*
   * Compute needle size (after checking).
   */
   if ( pNeedle )
   {
      needleSize = StrLen( pNeedle );
g_assert( needleSize > 0  &&  needleSize < MAX_SUBSTR_LEN );
      if (    needleSize < 1
           || needleSize > MAX_SUBSTR_LEN ) return NULL;
   }
   else
      return NULL;

  /*
   * Return NULL if any impossible case.
   */
   if (    pHaystackTop  == NULL
        || pHaystackBottom == NULL
        || *pHaystackTop == 0 
        || *pHaystackBottom == 0 
        || *pNeedle      == 0
        || ((pHaystackBottom + needleSize) > pHaystackTop )
      )
      return NULL;

  /*
   * Search backwards (from-top-to-bottom address).
   */
   needleFirstChar = pFuncCmp == strncmp ? *pNeedle : tolower( *pNeedle );
   pHaystackTop -= needleSize;
   while ( pHaystackTop >= pHaystackBottom )
   {
     /*
      * Does this haystack first char match first char in needle?
      */
      haystackTopChar = pFuncCmp == strncmp ? *pHaystackTop : tolower( *pHaystackTop );

      if ( needleFirstChar == haystackTopChar )
      {
        /*
         * Without overshooting, compare point in haystack against needle.
         */
         if ( (*pFuncCmp)( pHaystackTop, pNeedle, needleSize ) == 0 )
            return (char*)pHaystackTop;
      }

      --pHaystackTop;
   }

  /*
   * No match found.
   */
   return NULL;
}

/*****************************************************************************
 * Matches a regex in a segment.
 * @param   pHaystack, haystackLen
 *          Pointer/length of segment to search.
 * @param   pNeedle
 *          String that is sought in haystack.
 * @param   caseSens
 *          Whether to be case-sensitive.
 * @param   reverse
 *          If true, the search (conceptually) starts from the top of the haystack,
 *          scanning towards the bottom.
 * @param   cflags_newline
 *          Affects scope of matching.  Pass 0 or REG_NEWLINE.
 *          "If REG_NEWLINE is not set in cflags, then a <newline> in pattern
 *          or string shall be treated as an ordinary character."
 * @return  Returns a pointer/length of the match, else NULL.
 *          NULL can be returned because of a variety of failures,
 *          such as bad pointers or empty strings/matches.
 *****************************************************************************/
char*
FindRegexSegment( int*        pMatchLen,
                  const char* pHaystack,
                  int         haystackLen,
                  const char* pNeedle,
                  gboolean    caseSens,
                  gboolean    reverse,
                  int         cflags_newline )
{
   int          error           = 0;
   int          cflags;
   int          eflags;
   char*        pHaystackCopy   = NULL;
   const char*  pMatch          = NULL;
   const char*  pc;
   regex_t      re;
   regmatch_t   pmatch[1];

   *pMatchLen = 0;

   if (    pHaystack == NULL
        || haystackLen <= 0
        || haystackLen > MAX_HAYSTACK_LEN
        || pNeedle == NULL
        || strlen(pNeedle) == 0 )
      goto Return;

  /*
   * For regexec(), copy haystack as a null-terminated string.
   */
   pHaystackCopy = g_malloc( haystackLen + 1 );
   memcpy( pHaystackCopy, pHaystack, haystackLen );
   pHaystackCopy[haystackLen] = 0;

  /*
   * Call regcomp().
   */
   cflags = caseSens ? 0 : REG_ICASE; /* case-sensitive or not */
   cflags |= REG_EXTENDED | cflags_newline;
   if ( regcomp( &re, pNeedle, cflags ) )
      goto Return;

  /*
   * If this is a forward search, stop at the first match.
   * If this is a reverse search, stop at the last match.
   */
   for ( pc = pHaystackCopy; ; )
   {
     /*
      * Search for the next match.
      */
      eflags = IsEol( pc ) ? 0 : REG_NOTBOL;
      error = regexec( &re, pc, 1, pmatch, eflags );
      if ( error )
         break;

     /*
      * Found a match, save results.
      */
      pMatch     = (pc + pmatch[0].rm_so) - pHaystackCopy + pHaystack;
      *pMatchLen = pmatch[0].rm_eo - pmatch[0].rm_so;

     /*
      * Check results.
      */
         /* severe malfunction? */
      if (    pMatch <  pHaystack
           || pMatch >= (pHaystack + haystackLen)
           || *pMatchLen < 0 )
      {
         g_return_val_if_fail( 0, NULL );
      }
         /* bad match? (let caller catch too-big matches) */
      if (    (pMatch + *pMatchLen) > (pHaystack + haystackLen)
           || *pMatchLen == 0 )
      {
         pMatch     = NULL;
         *pMatchLen = 0;
         break;
      }

     /*
      * If searching forward, stop with this first match.
      */
      if ( !reverse )
         break;

     /*
      * For reverse searching, move pc past this match.
      * Continue unless past haystack.
      */
      pc += pmatch[0].rm_so + *pMatchLen;
      if ( pc >= (pHaystackCopy + haystackLen ) )
         break;
   }
   regfree( &re );

Return:
   g_free( pHaystackCopy );
   return (char*) pMatch;
}

/*****************************************************************************
 * Fast version of FindRegexSegment().
 * Case-sensitive forward search only and no checks.
 *****************************************************************************/
char*
FindRegexSegmentFast( int*        pMatchLen,
                      const char* pHaystack, /* -- ASCIIZ string -- */
                      const char* pNeedle )  /* regex pattern */

{
   const int    cflags          = REG_EXTENDED | REG_NEWLINE;
   const int    eflags          = 0;
   char*        pMatch          = NULL;
   regex_t      re;
   regmatch_t   pmatch[1];

   if ( regcomp( &re, pNeedle, cflags ) == 0 ) /* 0 if OK */
   {
      if ( regexec( &re, pHaystack, 1, pmatch, eflags ) == 0 ) /* 0 if OK */
      {
         pMatch     = (char*)pHaystack + pmatch[0].rm_so;
         *pMatchLen = pmatch[0].rm_eo - pmatch[0].rm_so;
      }
   }

   regfree( &re );
   return pMatch;
}

/*****************************************************************************
 * Count occurrences of a char in a ASCIIZ string.
 *****************************************************************************/
int
OccurrencesInString( const char* pc,
                     char        c )
{
   int cnt;
   for ( cnt = 0; *pc; ++pc ) cnt += (*pc == c);
   return cnt;
}

/*****************************************************************************
 * Count occurrences of a char in a segment.
 *****************************************************************************/
int
OccurrencesInSegment( const char* pc1,
                      const char* pc2,
                      char        c )
{
   int cnt;
   for ( cnt = 0; pc1 <= pc2; ++pc1 ) cnt += (*pc1 == c);
   return cnt;
}

/*****************************************************************************
 * Check the validity of a string.
 * @return FALSE if it appears invalid.
 *****************************************************************************/
gboolean
IsStringValid( const char* str, int maxLen )
{
   if ( str == NULL )
      return FALSE;

   if ( memchr( str, 0, maxLen ) == NULL )
      return FALSE;

   return TRUE;
}

/******************************************************************************
 * Free a glist whose elements are pointers to memory that was dynamically
 * allocated by ## g_alloc() ##.
 * @param   pGlistTags (OUT/GLOBAL)
 *          Assigned NULL.
 ******************************************************************************/
void
FreeGlistDynamic( GList* pGlist )
{
  /*
   * Send the pointer in each element to g_free().
   */
   g_list_foreach( pGlist, FreeGlistDynamicFreeElem, NULL );

   return;
}

/******************************************************************************
 * Free memory that is pointed to by an elem in a glist.
 * @param   pData
 *          Pointer to memory that was allocated by ## g_alloc() ##.
 * @param   pUnused
 *          (not used)
 ******************************************************************************/
void
FreeGlistDynamicFreeElem( gpointer pData,
                          gpointer pUnused )
{
   g_free( pData );
}

/*****************************************************************************
 * Use strcmp() as a hash key comparison.
 *****************************************************************************/
gint
HashStrcmp( gconstpointer pKey1,
            gconstpointer pKey2 )
{
#ifdef HYPERSRC_DEBUG
g_assert( pKey1 && pKey2 );
   return CompareStrings( (char*)pKey1,
                          (char*)pKey2 );
#else
   return strcmp( (char*)pKey1,
                  (char*)pKey2 );
#endif
}

/*****************************************************************************
 * Adaption of ElfHash.
 * @param   pKey
 * @param   h
 *          Pass this back to compute a hash from more than one string.
 *          Pass 0 for the first string.
 *****************************************************************************/
guint32
ElfHash( const guchar* pKey,
         guint32       h )
{
   guint32	g;

   do
   {
      h = ( h << 4 ) + *pKey++;
      if ( (g = (h & 0xf0000000)) )
         h ^= g >> 24;
      h &= ~g;
   }
   while ( *pKey );

   return h;
}

/*****************************************************************************
 * A faster replacement of g_list_append().
 * The difference is the third parm, which is a pointer to the last link.
 * The pointer will be updated every time this is called (in succession).
 * The purpose is to avoid calling the slow g_list_last() (as g_list_append()
 * would do) by remembering the last link resulting from appending.
 *****************************************************************************/
GList*
fast_g_list_append( GList*   list,
                    gpointer data,
                    GList**  ppLinkLast /* IN/OUT */ )
{
  GList *new_list;
  GList *last;
  
  new_list = g_list_alloc ();
  new_list->data = data;

 /*
  * ---This is a modification of GLIB source---
  */  
  if (list)
    {
      last = *ppLinkLast; /*jimb*/
      /* g_assert (last != NULL); */
      last->next = new_list;
      new_list->prev = last;

      *ppLinkLast = new_list; /*jimb*/
      return list;
    }
  else
  {
    *ppLinkLast = new_list; /*jimb*/
    return new_list;
  }
}

/*****************************************************************************
 * Extract the leading directory part of a pathname.
 * Can be used repetitively by feeding-back pPathname as ppLastDirChar+1
 * (called should check that char) and stopping when 0 is returned.
 * @param   pPathname
 * @param   ppDirBegin, ppDirEnd (OUT)
 *          Pointers to the first and last chars of directory part,
 *          or NULL if no dir is in the pathname.
 * @return  Length of dir part (excluding '/').
 *          0 is returned when the remainder is a filename.
 *****************************************************************************/
void
ExtractDirFromPathname( const char* pPathname,
                        char**      ppDirBegin, /*OUT*/
                        char**      ppDirEnd    /*OUT*/ )
{
   char* pMatch;

  /*
   * Initially assume no dir part is in pathname.
   */
   *ppDirBegin = *ppDirEnd = NULL;

#ifdef HYPERSRC_PEDANTIC
   g_return_if_fail( pPathname  &&  pPathname[0] );
#endif

  /*
   * The pathname could begin with '/', either because it is absolute,
   * or preceding dirs were extracted.  In either case, skip over '/'.
   */
   if ( pPathname[0] == '/' )
      ++pPathname;

  /*
   * Find a '/'.
   */
   pMatch = strchr( pPathname, '/' );

   if ( pMatch )
   {
     /*
      * Return pointers to the first/last chars of the dir part of pathname.
      */
      *ppDirBegin = (char*) pPathname;
      *ppDirEnd   = pMatch - 1;
#ifdef HYPERSRC_PEDANTIC
g_assert( *ppDirBegin <= *ppDirEnd );
#endif
   }

  /*
   * Pathname doesn't have a dir part.
   */
   return;
}

/*****************************************************************************
 * @return The count of chars in the path of pathname (excluding the filename).
 * Returns 0 if the pathname is entirely a filename.
 *****************************************************************************/
int
ExtractPathFromPathname( char* pPathname )
{
   char*	pMatch;

#ifdef HYPERSRC_PEDANTIC
   g_return_val_if_fail( pPathname  &&  pPathname[0], 0 );
#endif

  /*
   * Find last occurrence of '/'.
   */
   pMatch = strrchr( pPathname, '/' );

   if ( pMatch )
      return pMatch - pPathname;
   else
      return 0;
}

/*****************************************************************************
 * @return A pointer to the filename part of a pathname.
 *****************************************************************************/
char*
ExtractFilenameFromPathname( char* pPathname )
{
   char*	pMatch;

#ifdef HYPERSRC_PEDANTIC
   g_return_val_if_fail( pPathname  &&  pPathname[0], 0 );
#endif

  /*
   * Find last occurrence of '/'.
   */
   pMatch = strrchr( pPathname, '/' );

   if ( pMatch )
   {
#ifdef HYPERSRC_PEDANTIC
g_assert( pMatch[1] );
#endif
      return pMatch + 1;
   }
   else
      return pPathname;
}
